// Copyright (C) 2015, Alberto Corona <alberto@0x1a.us>
// All rights reserved. This file is part of core-utils, distributed under the
// GPL v3 license. For full terms please see the LICENSE file.

#![crate_type = "bin"]
#![crate_name = "cp"]
#![feature(path_ext,collections,path_relative_from)]

static VERS: &'static str = "0.1.0";
static PROG: &'static str = "cp";

extern crate getopts;
extern crate util;

use getopts::{Options};
use util::{Status};

use std::env;
use std::fs;
use std::io;
use std::fs::{PathExt};
use std::path::{PathBuf};

fn copy(from: &PathBuf, to: &PathBuf, verbose: bool) {
    match fs::copy(from, to) {
        Ok(_) => {
            if verbose {
                println!("{} -> {}", from.display(), to.display());
            }
        },
        Err(e) => {
            util::err(PROG, Status::Error, e.to_string());
            panic!(e.to_string());
        }
    };
}

fn copy_files(from: Vec<String>, to: &PathBuf, verbose: bool) {
    for file in from.iter() {
        if !to.is_dir() {
            util::err(PROG, Status::Error, String::from(String::from(
                        to.to_str().unwrap()) + " is not a directory"));
        } else {
            copy(&PathBuf::from(file), &to.join(PathBuf::from(file)), verbose);
        }
    }
}

fn copy_dir(from: &PathBuf, to: &PathBuf, verbose: bool) {
    if from.is_dir() {
        match fs::create_dir(&to) {
            Ok(_) => {
                if verbose {
                    println!("{} -> {}", from.display(), to.display());
                }
            },
            Err(e) => {
                panic!(e.to_string());
            }
        };
    }
}

fn walk_dir_copy(from: &PathBuf, mut to: PathBuf, verbose: bool) -> io::Result<()> {
    if from.is_dir() {
        if !to.exists() {
            copy_dir(&from, &to, verbose);
        }
        for cont in try!(fs::read_dir(&from)) {
            let cont = match cont {
                Ok(s) => { s },
                Err(e) => {
                    util::err(PROG, Status::Error, e.to_string());
                    panic!();
                }
            };
            if cont.path().is_file() {
                copy(&cont.path(),
                &to.clone().join(cont.path().file_name().unwrap()), verbose);
            } else if cont.path().is_dir() {
                to.push(cont.path().relative_from(&from).unwrap());
                if from.has_root() || to.has_root() {
                    if from.components().last().unwrap().as_os_str() ==
                        to.parent().unwrap().components().last().unwrap().as_os_str() {
                            util::err(PROG, Status::Error,
                                      String::from("cannot copy a directory into itself"));
                        }
                }
                if from.components().last().unwrap().as_os_str() ==
                    to.components().nth(1).unwrap().as_os_str() {
                        util::err(PROG, Status::Error,
                                  String::from("cannot copy a directory into itself"));
                    }
                match walk_dir_copy(&cont.path(), to.clone(), verbose) {
                    Ok(s) => { s },
                    Err(e) => {
                        util::err(PROG, Status::Error, e.to_string());
                        panic!();
                    }
                }
                to.pop();
            }
        }
    }
    return Ok(());
}

fn print_usage(prog: &str, opts: Options) {
    let brief = format!("Usage: {} [OPTION]", prog);
    print!("{}", opts.usage(&brief));
}

fn main() {
    let args: Vec<String> = env::args().collect();
    let mut opts = Options::new();

    opts.optflag("h", "help", "Print the help menu");
    opts.optflag("", "version", "Print the version");
    opts.optflag("v", "verbose", "Print operation verbosely");
    opts.optflag("r", "recursive", "Recursively copy a directory");

    let matches = match opts.parse(&args[1..]) {
        Ok(m) => { m },
        Err(e) => {
            util::err(PROG, Status::OptError, e.to_string());
            panic!(e.to_string())
        }
    };

    if matches.opt_present("h") {
        print_usage(PROG, opts);
    } else if matches.opt_present("version") {
        util::copyright(PROG, VERS, "2015", vec!["Alberto Corona"]);
    } else {
        let verb = matches.opt_present("v");
        let src = match matches.free.first().clone() {
            Some(s) => { PathBuf::from(s) },
            None => {
                println!("{}: no arguments given", PROG);
                util::exit(Status::OptError);
                panic!();
            }
        };
        if matches.free.len() == 1 {
            println!("{}: no destination for {}", PROG, src.display());
            util::exit(Status::Error);
        }
        if matches.opt_present("r") {
            for items in matches.free.tail() {
                match walk_dir_copy(&src, PathBuf::from(items), verb) {
                    Ok(_) => { },
                    Err(e) => {
                        util::err(PROG, Status::Error, e.to_string());
                        panic!();
                    }
                };
            }
        } else {
            let dest = PathBuf::from(matches.free.last().unwrap());
            if matches.free.len() > 2 {
                copy_files(matches.free.init().to_vec(), &dest, verb);
            } else {
                copy(&src, &PathBuf::from(&matches.free[1]), verb);
            }
        }
    }
}
